package com.zqxq.common.utils;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * Created by 花生 on 2017-08-04.
 * desc:开放云签名工具类
 */
public class RSASignUtils {
    /**RSA签名算法-MD2withRSA**/
    private final static String RSA_ALGORITHM_MD2withRSA = "MD2withRSA";
    /**RSA签名算法-MD5withRSA**/
    private final static String RSA_ALGORITHM_MD5withRSA = "MD5withRSA";
    /**RSA签名算法-SHA1withRSA**/
    private final static String RSA_ALGORITHM_SHA1withRSA = "SHA1withRSA";
    /**RSA签名算法-SHA256withRSA**/
    private final static String RSA_ALGORITHM_SHA256withRSA = "SHA256withRSA";
    /**RSA签名算法-SHA384withRSA**/
    private final static String RSA_ALGORITHM_SHA384withRSA = "SHA384withRSA";
    /**RSA签名算法-SHA512withRSA**/
    private final static String RSA_ALGORITHM_SHA512withRSA = "SHA512withRSA";
    /**
     * 默认字符集编码
     */
    private final static String DEFAULT_CHARSET_NAME = "UTF-8";
    /**
     * 获取签名后的数据
     * @param dataJson  数据json字符串
     * @param app_id    商户给平台的app_id
     * @param privateKeyStr 平台给商户的私钥
     * @return 签名后的集合
     */
    public static Map<String,String> getSignedMap(String dataJson,String app_id,String privateKeyStr){
        if(StringUtils.isNotBlank(dataJson) && StringUtils.isNotBlank(privateKeyStr)){
            Map<String,String> postMap = getPostMap(dataJson,app_id);
            //增加一个秘密字段，只参与签名不参与传输
            postMap.put("secret","zqxq");
            //生成待签名字符串
            String sortSignStr = getSortSignStr(postMap);
            if(StringUtils.isNotBlank(sortSignStr)){
                // 生成签名
                String sign = sign(sortSignStr,privateKeyStr);
                if(StringUtils.isNotBlank(sign)){
                    postMap.put("sign",sign);
                    //移除秘密字段
                    postMap.remove("secret");
                    return postMap;
                }
            }
        }
        return null;
    }

    /**
     * 获取访问商户接口时待排序的集合
     * @param dataJson  要传输的数据，转为json字符串
     * @param app_id 商户给平台的app_id（可以没有）
     * @return  待签名集合
     */
    public static Map<String,String> getPostMap(String dataJson,String app_id){
        Map<String,String> postMap = new HashMap();
        postMap.put("app_id",app_id);
        postMap.put("sign_type","RSA");
        postMap.put("biz_data", dataJson);
        postMap.put("format","json");
        postMap.put("version","1.0");
        postMap.put("timestamp",String.valueOf(new Date().getTime()/1000));//10位时间戳,精确到秒
        return  postMap;
    }

    public static String getSortSignStr(JSONObject params){
        StringBuffer content = new StringBuffer();
        // 按照key做首字母升序排列
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);
        for (int i = 0; i < keys.size(); i++) {
            String key =  keys.get(i);
            if ("sign".equals(key)) {//sign不参与签名
                continue;
            }
            String value = params.getString(key);
            if (StringUtils.isEmpty(value)) { // 空串不参与签名
                continue;
            }
            content.append((i == 0 ? "" : "&") + key + "=" + value);
        }
        String sortStr = content.toString();
        if(sortStr.startsWith("&") || sortStr.startsWith("=")){
            sortStr = sortStr.replaceFirst("&|=","");
        }
        return sortStr;
    }
    /**
     * 生成待签名字符串
     * @param params    待排序的集合
     * @return  待签名字符串
     */
    public static String getSortSignStr(Map<String, String> params){
        StringBuffer buffer = new StringBuffer();
        Map<String,String> map = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //升序排列
                return o1.compareTo(o2);
            }
        });
        for (String key: params.keySet()) {
            map.put(key, params.get(key));
        }
        Set<String> keySet = map.keySet();
        Iterator<String> iter = keySet.iterator();
        while(iter.hasNext()){
            String key = iter.next();
            String value = map.get(key);
            if(StringUtils.isNotBlank(value)){//空串不参与签名
                if(!"sign".equalsIgnoreCase(key)){//sign字段不参与签名
                    buffer.append(key).append("=").append(value).append("&");
                }
            }
        }
        if(buffer.length()>0){
            return buffer.substring(0,buffer.length()-1);
        }
        return "";
    }

    /**
     * RSA签名（默认使用SHA256withRSA算法）
     * @param data          待签名字符串
     * @param privateKeyStr 私钥字符串
     * @return  签名字符串
     */
    public static String sign(String data,String privateKeyStr){
        return sign(data,privateKeyStr,RSA_ALGORITHM_SHA256withRSA);
    }

    /***
     * RSA签名（自定义使用RSA算法）
     * @param data          待签名字符串
     * @param privateKeyStr 私钥字符串
     * @param algorithm 签名字符串
     * @return
     */
    public static String sign(String data,String privateKeyStr,String algorithm){
        if(StringUtils.isNotBlank(data) && StringUtils.isNotBlank(privateKeyStr)){
            byte[] dataByte = new byte[0];
            try {
                dataByte = data.getBytes(DEFAULT_CHARSET_NAME);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            RSAPrivateKey privateKey = initRSAPrivateKeyByStr(privateKeyStr);
            return Base64.encodeBase64String(signByPrivateKey(dataByte,privateKey,algorithm));
        }
        return "";
    }
    /**
     * RSA私钥签名
     * @param data  待签名字符串数组
     * @param privateKey 私钥
     * @param algorithm RSA签名算法
     * @return 返回base64字符串数组
     */
    public static byte[] signByPrivateKey(byte[] data,PrivateKey privateKey,String algorithm){
        try {
            Signature signature = Signature.getInstance(algorithm);
            if(privateKey!=null){
                signature.initSign(privateKey);
                signature.update(data);
                return signature.sign();
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * RSA验证签名
     * @param publicKeyStr  公钥字符串
     * @param result        返回字符串
     * @return  验证签名是否相同
     */
    public static boolean verify(String publicKeyStr,String result){
        if(StringUtils.isNotBlank(publicKeyStr) && StringUtils.isNotBlank(result)){
            //result转换
            Map<String,String> resultMap = JSONObject.parseObject(result,Map.class);
            if(resultMap!=null && !resultMap.isEmpty()){
                //获取传递过来的签名
                String sign = resultMap.get("sign");
                //增加秘密字段，参与签名验证
                resultMap.put("secret","zqxq");
                //获取待签名字符串
                String data = getSortSignStr(resultMap);
                return verify(publicKeyStr,data,sign);
            }
        }
        return false;
    }
    /**
     * RSA验证签名(默认使用RSA算法SHA256withRSA)
     * @param publicKeyStr  公钥字符串
     * @param data  url排序后数据字符串
     * @param sign  签名字符串
     * @return 验证签名是否相同
     */
    public static boolean verify(String publicKeyStr,String data,String sign){
        return verify(publicKeyStr,data,sign,RSA_ALGORITHM_SHA256withRSA);
    }
    /**
     * RSA验证签名(自定义使用RSA算法)
     * @param publicKeyStr  公钥字符串
     * @param data  url排序后数据字符串
     * @param sign  签名字符串
     * @return 验证签名是否相同
     */
    public static boolean verify(String publicKeyStr,String data,String sign,String algorithm){
        if(StringUtils.isNotBlank(publicKeyStr) && StringUtils.isNotBlank(data) && StringUtils.isNotBlank(sign)){
            RSAPublicKey publicKey = initRSAPublicKeyByStr(publicKeyStr);
            byte[] dataBytes = new byte[0];
            try {
                dataBytes = data.getBytes(DEFAULT_CHARSET_NAME);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            byte[] signBytes = Base64.decodeBase64(sign);
            return verifyByPublicKey(publicKey,dataBytes,signBytes,algorithm);
        }
        return false;
    }

    /**
     * RSA公钥验证签名
     * @param publicKey RSA公钥
     * @param data  数据byte数组
     * @param sign  签名byte数组
     * @return 验证签名是否相同
     */
    public static boolean verifyByPublicKey(RSAPublicKey publicKey,byte[] data,byte[] sign,String algorithm){
        try {
            Signature signature = null;
            if(StringUtils.isNotBlank(algorithm)){
                signature = Signature.getInstance(algorithm);
            }else{
                signature = Signature.getInstance(RSA_ALGORITHM_SHA256withRSA);
            }
            signature.initVerify(publicKey);
            signature.update(data);
            return signature.verify(sign);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return false;
    }
    /**
     * 从字符串加载RSA公钥
     * @param publicKeyStr  公钥字符串
     * @return  公钥或null
     */
    public static RSAPublicKey initRSAPublicKeyByStr(String publicKeyStr){
        if(StringUtils.isNotBlank(publicKeyStr)){
            //转换公钥字符串为base64字符串
            byte[] base64 = Base64.decodeBase64(publicKeyStr);
            try {
                //调用RSA的工厂类，依x509规则生成RSA公钥
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64);
                return (RSAPublicKey) keyFactory.generatePublic(keySpec);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 从字符串加载RSA私钥
     * @param privateKeyStr 私钥字符串
     * @return  私钥或null
     */
    public static RSAPrivateKey initRSAPrivateKeyByStr(String privateKeyStr){
        if(StringUtils.isNotBlank(privateKeyStr)){
            try {
                byte[] privateByte = Base64.decodeBase64(privateKeyStr);
                //调用RSA的工厂类，依PKCS8规则生成RSA私钥
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateByte);
                return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
